// Tierless
// Copyright © 2010 Alphora
// This file is licensed under a modified BSD-license (see tierless_license.txt).
// NOTE: This class is based on the TransactionScopeConnections class from the Enterprise Library Data Access Block
// It was modified here to be able to use the Tierless Database class, instead of the Enterprise Library one.
using System.Collections.Generic;
using System.Data.Common;
using System.Transactions;

namespace Tierless.Infrastructure.Data
{
    public static class TransactionScopeConnections
    {
        // There's a reason why this field is not thread-static: notifications for completed oracle transactions
        // may happen in a different thread
        static readonly Dictionary<Transaction, Dictionary<string, DbConnection>> TransactionConnections =
            new Dictionary<Transaction, Dictionary<string, DbConnection>>();

        public static DbConnection GetConnection(Database database)
        {
            var currentTransaction = Transaction.Current;

            if (currentTransaction == null)
                return null;

            Dictionary<string, DbConnection> connectionList;
            DbConnection connection;

            lock (TransactionConnections)
            {
                if (!TransactionConnections.TryGetValue(currentTransaction, out connectionList))
                {
                    // We don't have a list for this transaction, so create a new one
                    connectionList = new Dictionary<string, DbConnection>();
                    TransactionConnections.Add(currentTransaction, connectionList);

                    // We need to know when this previously unknown transaction is completed too
                    currentTransaction.TransactionCompleted += OnTransactionCompleted;
                }
            }

            lock (connectionList)
            {
                // Next we'll see if there is already a connection. If not, we'll create a new connection and add it
                // to the transaction's list of connections.
                // This collection should only be modified by the thread where the transaction scope was created
                // while the transaction scope is active.
                // However there's no documentation to confirm this, so we err on the safe side and lock.
                if (!connectionList.TryGetValue(database.ConnectionString, out connection))
                {
                    // we're betting the cost of acquiring a new finer-grained lock is less than 
                    // that of opening a new connection, and besides this allows threads to work in parallel
                    connection = database.CreateConnection();
                    connectionList.Add(database.ConnectionString, connection);
                }
            }

            return connection;
        }

        static void OnTransactionCompleted(object sender, TransactionEventArgs e)
        {
            Dictionary<string, DbConnection> connectionList;

            lock (TransactionConnections)
            {
                if (!TransactionConnections.TryGetValue(e.Transaction, out connectionList))
                {
                    // we don't know about this transaction. odd.
                    return;
                }

                // we know about this transaction - remove it from the mappings
                TransactionConnections.Remove(e.Transaction);
            }

            lock (connectionList)
            {
                // acquiring this lock should not be necessary unless there's a possibility for this event to be fired
                // while the transaction involved in the event is still set as the current transaction for a 
                // different thread.
                foreach (var connection in connectionList.Values)
                {
                    connection.Dispose();
                }
            }
        }
    }
}
